
# ------------------------------------------------------------------------------------------
# ## 函数
#
# 话题：
# 1. 如何声明一个函数
# 2. Julia中的鸭子类型（Duck-typing）
# 3. 原地修改与非原地修改函数 Mutating vs. non-mutating functions
# 4. 高阶函数
# ------------------------------------------------------------------------------------------



# ------------------------------------------------------------------------------------------
# ## 如何声明一个函数
# Julia提供了几种不同的声明函数的方法。第一种方法是用`function` 和 `end` 关键字
# ------------------------------------------------------------------------------------------

function sayhi(name)
    println("Hi $name, it's great to see you!")
end

function f(x)
    x^2
end

# ------------------------------------------------------------------------------------------
# 我们可以这样调用函数：
# ------------------------------------------------------------------------------------------

sayhi("C-3PO")

f(42)

# ------------------------------------------------------------------------------------------
# 等价的，我们可以只用一行声明函数，拿上面两个函数为例
# ------------------------------------------------------------------------------------------

sayhi2(name) = println("Hi $name, it's great to see you!")

f2(x) = x^2

sayhi2("R2D2")

f2(42)

# ------------------------------------------------------------------------------------------
# 最后，我们可以声明“匿名”函数
# ------------------------------------------------------------------------------------------

sayhi3 = name -> println("Hi $name, it's great to see you!")

f3 = x -> x^2

sayhi3("Chewbacca")

f3(42)

# ------------------------------------------------------------------------------------------
# ## Julia中的鸭子类型（Duck-typing）
# *“假如它嘎嘎像个鸭鸭，那它就是个鸭鸭”* <br><br>
# 不管输入参数是什么类型，只要能用，那么Julia函数就能使 <br><br>
#
# 例如，函数`sayhi`可以接受这个写作整数的小电视角色的名字……
# ------------------------------------------------------------------------------------------

sayhi(55595472)

# ------------------------------------------------------------------------------------------
# 函数`f`可以接受一个矩阵【译注：方阵】
# ------------------------------------------------------------------------------------------

A = rand(3, 3)
A

f(A)

# ------------------------------------------------------------------------------------------
# 函数`f`也可以接受字符串比如"hi"，因为`*`在接受字符串时用作字符串拼接。
# ------------------------------------------------------------------------------------------

f("hi")

# ------------------------------------------------------------------------------------------
# 然而，`f`函数无法接受一个向量（vector）。不同于`A^2`是已经定义好的矩阵乘方，当`v`是向量时的`v^2`是是个没有定义的代数运算。
# ------------------------------------------------------------------------------------------

v = rand(3)

f(v)

# ------------------------------------------------------------------------------------------
# ## 原地修改与非原地修改函数 Mutating vs. non-mutating functions
#
# 约定：函数名以`!`结束的原地修改传入的变量，函数名非以`!`结束的则不会改变传入的变量。
#
# 例如下面的`sort`和`sort!`
# ------------------------------------------------------------------------------------------

v = [3, 5, 2]

sort(v)

v

# ------------------------------------------------------------------------------------------
# `sort(v)`会返回一个新的、将`v`排序后的数组，`v`本身不会改变。 <br><br>
#
# 而当执行`sort!(v)`后，`v`将会被原地排序修改。
# ------------------------------------------------------------------------------------------

sort!(v)

v

# ------------------------------------------------------------------------------------------
# ## 一些高阶函数
#
# ### map
#
# `map`是Julia中的高阶函数。“高阶”指的是它会*接受一个函数*作为它的传入参数。
# `map`将传入的函数作用于传入的数据结构的每一个元素。例如执行
#
# ```julia
# map(f, [1, 2, 3])
# ```
# 将得到一个输出数组，数组的每一个元素为`f`作用在数组`[1, 2, 3]`的对应元素
# ```julia
# [f(1), f(2), f(3)]
# ```
# ------------------------------------------------------------------------------------------

map(f, [1, 2, 3])

# ------------------------------------------------------------------------------------------
# 这里我们把向量`[1, 2, 3]`的每一个元素都平方了，而不是对向量`[1, 2, 3]`本身进行平方。
#
# `map`可以接受匿名函数作为参数，像这样
# ------------------------------------------------------------------------------------------

x -> x^3

# ------------------------------------------------------------------------------------------
# 通过
# ------------------------------------------------------------------------------------------

map(x -> x^3, [1, 2, 3])

# ------------------------------------------------------------------------------------------
# 这就完成了对数组`[1, 2, 3]`所有元素的三次方计算！
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ### broadcast
#
# `broadcast`和`map`一样也是个高阶函数。`broadcast`一种广义化的`map`,所以`map`能做的它都能做，并不止如此。`broadcast`的调用方法和`m
# ap`一样。
# ------------------------------------------------------------------------------------------

broadcast(f, [1, 2, 3])

# ------------------------------------------------------------------------------------------
# 我们又一次通过函数`f`把向量`[1, 2, 3]`的每一个元素都平方了——这次是用“广播”函数`f`的方式！
#
# `broadcast`有个语法糖：调用要广播的函数时在函数名和输入变量之间加一个`.`。比如：
#
# ```julia
# broadcast(f, [1, 2, 3])
# ```
# 就等价于
# ```julia
# f.([1, 2, 3])
# ```
# ------------------------------------------------------------------------------------------

f.([1, 2, 3])

# ------------------------------------------------------------------------------------------
# 再一次强调`map`和`broadcast`方式与如下直接调用函数的区别
# ```julia
# f([1, 2, 3])
# ```
# 我们可以求向量中每个元素的平方，但是不能求向量的平方！
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# 为了再讲清楚这一点，我们再看一下以下调用的区别
#
# ```julia
# f(A)
# ```
# 与
# ```julia
# f.(A)
# ```
# 其中`A`是个矩阵：
# ------------------------------------------------------------------------------------------

A = [i + 3*j for j in 0:2, i in 1:3]

f(A)

# ------------------------------------------------------------------------------------------
# 和之前一样，对于矩阵`A`来说
# ```
# f(A) = A^2 = A * A
# ```
#
# 另一方面，
# ------------------------------------------------------------------------------------------

B = f.(A)

# ------------------------------------------------------------------------------------------
# 包含了矩阵`A`中每个元素的平方
#
# 广播的点语法让比较复杂的元素间运算的表示更自然、更贴近数学表示，比如：
# ------------------------------------------------------------------------------------------

A .+ 2 .* f.(A) ./ A

# ------------------------------------------------------------------------------------------
# 而不是
# ------------------------------------------------------------------------------------------

broadcast(x -> x + 2 * f(x) / x, A)

# ------------------------------------------------------------------------------------------
# 这两种方法本质上完全一样
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# ### 练习
#
# #### 6.1
# 声明一个函数`add_one`用来给它的输入参数加1。
# ------------------------------------------------------------------------------------------

@assert add_one(1) == 2

@assert add_one(11) == 12

# ------------------------------------------------------------------------------------------
# #### 6.2
# 使用函数`map`或者`broadcast`给矩阵`A`的每一个元素加1并把它赋值给`A1`。
# ------------------------------------------------------------------------------------------



# ------------------------------------------------------------------------------------------
# #### 6.3
# 使用广播的点语法给矩阵`A`的每一个元素加1并把它赋值给`A2`。
# ------------------------------------------------------------------------------------------



@assert A2 == [3 4 5; 6 7 8;9 10 11]

# ------------------------------------------------------------------------------------------
# 请在完成练习后点击顶部的`Validate`。
# ------------------------------------------------------------------------------------------
